package com.bx.imserver.netty.ws;

import com.bx.imserver.netty.IMChannelHandler;
import com.bx.imserver.netty.IMServer;
import com.bx.imserver.netty.ws.endecode.MessageProtocolDecoder;
import com.bx.imserver.netty.ws.endecode.MessageProtocolEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * WS服务器,用于连接网页的客户端,协议格式: 直接IMSendInfo的JSON序列化
 *
 * @author Blue
 * @date 2022-11-20
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "websocket", value = "enable", havingValue = "true", matchIfMissing = false)
public class WebSocketServer implements IMServer {

    @Value("${websocket.port}")
    private int port;

    private volatile boolean ready = false;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;


    @Override
    public boolean isReady() {
        return ready;
    }

    /**
     * WebSocket其他特点如下：
     * （1）建立在 TCP 协议之上，服务器端的实现比较容易。
     * （2）与 HTTP 协议有着良好的兼容性。默认端口也是80和443，并且握手阶段采用 HTTP 协议，因此握手时不容易屏蔽，能通过各种 HTTP 代理服务器。
     * （3）数据格式比较轻量，性能开销小，通信高效。
     * （4）可以发送文本，也可以发送二进制数据。
     * （5）没有同源限制，客户端可以与任意服务器通信。
     * （6）协议标识符是ws（如果加密，则为wss-可以联想到https），服务器网址就是 URL。
     * 1)WebSocket与HTTP的关系
     *  相同点：
     *    1,都是一样基于TCP的，都是可靠性传输协议。
     *    2,都是应用层协议。
     *  不同点：
     *    1,WebSocket是双向通信协议，模拟Socket协议，可以双向发送或接受信息。HTTP是单向的。
     *    2,WebSocket是需要握手进行建立连接的。
     * 联系:
     *  WebSocket在建立握手时，数据是通过HTTP传输的。但是建立之后，两者之间后续进行直接的数据互相传送，不需要HTTP协议的（之后通信都是基于tcp协议的）
     * 2)WebSocket与Socket的关系
     *    1,Socket其实并不是一个协议，而是为了方便使用TCP或UDP而抽象出来的一层，是位于应用层和传输控制层之间的一组接口。
     *    2,Socket是应用层与TCP/IP协议族通信的中间软件抽象层，它是一组接口。在设计模式中，Socket其实就是一个门面模式，
     *      它把复杂的TCP/IP协议族隐藏在Socket接口后面，对用户来说，一组简单的接口就是全部，让Socket去组织数据，以符合指定的协议。
     *    3,当两台主机通信时，必须通过Socket连接，Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议，IP协议的连接则依赖于链路层等更低层次。
     *      WebSocket则是一个典型的应用层协议。
     *  WebSocket 机制:
     *    1,WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信，能更好的节省服务器资源和带宽并达到实时通讯，
     *      它建立在 TCP 之上，同 HTTP 一样通过 TCP 来传输数据，但是它和 HTTP 最大不同是：
     *         1:WebSocket 是一种双向通信协议，在建立连接后，WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据，就像 Socket 一样；
     *         2:WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接，连接成功后才能相互通信
     * 2)websocket 请求报文:
     *    GET /webfin/websocket/ HTTP/1.1
     *    Host: localhost
     *    Upgrade: websocket
     *    Connection: Upgrade
     *    Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
     *    Origin:
     *    http://localhost
     *    :8080
     *    Sec-WebSocket-Version: 13
     *    http://localhost
     *    :8080
     *    Sec-WebSocket-Version: 13
     * 可以看到，客户端发起的 WebSocket 连接报文类似传统 HTTP 报文，”Upgrade：websocket”参数值表明这是 WebSocket 类型请求
     * ，“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文，
     * 要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答，
     * 否则客户端会抛出“Error during WebSocket handshake”错误，并关闭连接。
     * 3)WebSocket 服务端响应报文:
     *    HTTP/1.1 101 Switching Protocols
     *    Upgrade: websocket
     *    Connection: Upgrade
     *    Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
     * “Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,
     * “HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接，
     * 经过这样的请求-响应处理后，客户端服务端的 WebSocket 连接握手成功, 后续通信全部使用websocket（底层是tcp）自己的协议。
     *
     * （WebSocket在建立握手时，数据是通过HTTP传输的。但是建立之后，在真正传输时候是不需要HTTP协议的。）
     */
    @Override
    public void start() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        // 设置为主从线程模型
        bootstrap.group(bossGroup, workGroup)
                // 设置服务端NIO通信类型
                .channel(NioServerSocketChannel.class)
                // 设置ChannelPipeline，也就是业务职责链，由处理的Handler串联而成，由从线程池处理
                .childHandler(new ChannelInitializer<Channel>() {
                    // 添加处理的Handler，通常包括消息编解码、业务处理，也可以是日志、权限、过滤等
                    @Override
                    protected void initChannel(Channel ch) {
                        // 获取职责链
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
                        pipeline.addLast("http-codec", new HttpServerCodec());
                        pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
                        pipeline.addLast("http-chunked", new ChunkedWriteHandler());
                        pipeline.addLast(new WebSocketServerProtocolHandler("/im"));
                        pipeline.addLast("encode", new MessageProtocolEncoder());
                        pipeline.addLast("decode", new MessageProtocolDecoder());
                        pipeline.addLast("handler", new IMChannelHandler());
                    }
                })
                // bootstrap 还可以设置TCP参数，根据需要可以分别设置主线程池和从线程池参数，来优化服务端性能。
                // 其中主线程池使用option方法来设置，从线程池使用childOption方法设置。
                // backlog表示主线程池中在套接口排队的最大数量，队列由未连接队列（三次握手未完成的）和已连接队列
                .option(ChannelOption.SO_BACKLOG, 5)
                // 表示连接保活，相当于心跳机制，默认为7200s
                .childOption(ChannelOption.SO_KEEPALIVE, true);

        try {
            // 绑定端口，启动select线程，轮询监听channel事件，监听到事件之后就会交给从线程池处理
            bootstrap.bind(port).sync().channel();
            // 就绪标志
            this.ready = true;
            log.info("websocket server 初始化完成,端口：{}", port);
            // 等待服务端口关闭
            //channel.closeFuture().sync();
        } catch (InterruptedException e) {
            log.info("websocket server 初始化异常", e);
        }
    }

    @Override
    public void stop() {
        if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isShutdown()) {
            bossGroup.shutdownGracefully();
        }
        if (workGroup != null && !workGroup.isShuttingDown() && !workGroup.isShutdown()) {
            workGroup.shutdownGracefully();
        }
        this.ready = false;
        log.info("websocket server 停止");
    }


}
